﻿using System;
using System.Collections;
using System.Net;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Collections.Generic;
using Rodunu.Movies.Services.Models.Movies;
using Rodunu.Utilities.HtmlAgilityPack;

namespace Rodunu.Movies.Services.IMDB
{
    /// <summary>
    /// Class to execute the first steps of a search
    /// </summary>
    public class Controller
    {
        private string page;
        private string m_title;
        private Title title;
        private string tit, temp;
        //maximum of 3 genres per title
        private string[] t_genres = new string[3];
        //maximum of 5 actors per title
        private string[] t_actors = new string[5];
        private int ind, index;
        private System.ComponentModel.BackgroundWorker worker;

        public Controller()
        {
            
        }

        #region Search

        public IList<SearchResult> SearchByTitle(string searchBy)
        {
            LoadPage("http://www.imdb.com/find?s=all&q=" + searchBy + "&x=0&y=0");
            return ParseSearch();
        }

        public IList<SearchResult> SearchById(string searchBy)
        {
            LoadPage("http://www.imdb.com/title/" + searchBy + "/");
            return ParseSearch();
        }

        private void LoadPage(string url)
        {
            try
            {
                var myRequest = (HttpWebRequest)WebRequest.Create(url);
                myRequest.Method = "GET";
                WebResponse myResponse = myRequest.GetResponse();
                var sr = new StreamReader(myResponse.GetResponseStream(), Encoding.UTF8);
                page = sr.ReadToEnd();
                sr.Close();
                myResponse.Close();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private string GetPage(string url)
        {
            string pg = string.Empty;
            try
            {
                var myRequest = (HttpWebRequest)WebRequest.Create(url);
                myRequest.Method = "GET";
                WebResponse myResponse = myRequest.GetResponse();
                var sr = new StreamReader(myResponse.GetResponseStream(), Encoding.UTF8);
                pg = sr.ReadToEnd();
                sr.Close();
                myResponse.Close();
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return pg;
        }

        /// <summary>
        /// Parses the search results, to get the links
        /// </summary>
        /// <returns>An array of links of popular titles and exact matches</returns>
        private IList<SearchResult> ParseSearch()
        {
            IList<SearchResult> titles = new List<SearchResult>();
            bool pop = true;
            bool ex = true;

            ind = page.IndexOf("Popular Titles");
            if (ind < 0)
            {
                pop = false;
                ind = page.IndexOf("Titles (Exact Matches)");
                if (ind < 0)
                {
                    ex = false;
                }
            }
            if (ex)
            {
                var regex = new Regex(@"<a href=""/title/tt[A-Za-z0-9]*/"">[A-Za-z0-9-\s]*</a>\s\([0-9]{4}\)");
                var idReg = new Regex("tt[A-Za-z0-9]*");
                var nameReg = new Regex(@">[A-Za-z0-9-\s]*<");
                var yearReg = new Regex(@"\([0-9]{4}\)");
                
                var matches = regex.Matches(page);
                foreach (var match in matches)
                {
                    var result = new SearchResult();
                    result.Id = idReg.Match(match.ToString()).ToString().Substring(2);
                    result.Name = nameReg.Match(match.ToString()).ToString().Substring(1).TrimEnd('<');
                    result.Name += " " + yearReg.Match(match.ToString());
                    
                    bool exists = false;
                    foreach (var name in titles)
                    {
                        if (name.Id == result.Id)
                            exists = true;
                    }
                    if (!exists)
                        titles.Add(result);
                        
                }
                
                return titles;
            }
            return new List<SearchResult>();
        }

        #endregion
        
        /// <summary>
        /// Gets the page type
        /// </summary>
        ///  <param name="formato"> Defines if it's to search a movie or a tv show
        ///  0 - movie
        ///  1 - tv show</param>
        /// <returns>If the page is a result list page - 1
        /// or the actual result - 0</returns>
        public int getType(int formato)
        {
            ParseHTML parse = new ParseHTML();
            parse.Source = page;
            bool b = true;
            while (!parse.Eof() && b)
            {
                char ch = parse.Parse();
                if (ch == 0)
                {
                    AttributeList tag = parse.GetTag();
                    if (tag["content"] != null)
                    {
                        string res = tag["content"].Value;
                        res = res.ToLower();
                        string t = "IMDb Search";
                        if (formato == 0 && res.Contains(m_title.ToLower()) && !page.Contains("TV series"))
                        {
                            m_title = tag["content"].Value;
                            b = false;
                            return 0;
                        }
                        else if (formato == 0 && res.Contains(t.ToLower()))
                        {
                            b = false;
                            return 1;
                        }
                        else if (formato == 1 && res.Contains(m_title.ToLower()) && page.Contains("TV series"))
                        {
                            m_title = tag["content"].Value;
                            b = false;
                            return 0;
                        }
                        else if (formato == 1 && res.Contains(t.ToLower()))
                        {
                            b = false;
                            return 1;
                        }
                    }
                }
            }
            return -1;
        }
        
        #region Parse Attributes

        /// <summary>
        /// Gets the title from IMDb
        /// </summary>
        private void parseTitle()
        {
            var regex = new Regex(@"<title>[A-Za-z0-9-\s-\(-\)]*</title>").Match(page);
            title.Name = regex.ToString().Substring(7, (regex.ToString().IndexOf("</title>") - 7));
        }

        /// <summary>
        /// Gets the year from IMDb
        /// </summary>
        private void parseYear()
        {
            var regex = new Regex(@"<h5>Release Date:</h5>[A-Za-z0-9-\s-]*\(").Match(page).ToString();
            if (regex.Length > 0)
                title.ReleaseDate = regex.Substring(24, (regex.IndexOf("(") - 25));
        }

        /// <summary>
        /// Gets the cover link from IMDb
        /// </summary>
        private void parseCoverLink()
        {
            ind = page.IndexOf("\"poster\"");
            index = 0;
            temp = "";
            if (ind != -1 && !page.Contains("http://ia.media-imdb.com/media/imdb/01/I/37/58/83/10.gif"))
            {
                temp = page.Substring(ind + 90, 300);
                index = ind + 90 + temp.Length;
                temp = temp.Substring(temp.IndexOf("src") + 5);
                temp = temp.Substring(0, temp.IndexOf("\""));
                title.ImageUrl = temp;
                if (!temp.Contains(".gif"))
                    page = page.Substring(index);
                else title.ImageUrl = "";
            }
        }

        /// <summary>
        /// Gets the link from IMDb
        /// </summary>
        private void parseLink()
        {
            title.IMDB = "http://www.imdb.com" + page.Substring(page.IndexOf("/title/"), 17);
        }

        /// <summary>
        /// Gets the rate from IMDb
        /// </summary>
        private void parseRate()
        {
            try
            {
                ind = -1;
                temp = "";
                ind = page.IndexOf("User Rating:");
                if (ind != -1)
                {
                    temp = page.Substring(ind + 21, 10);
                    index = ind + 21 + temp.Length;
                    title.Rating = Convert.ToInt32(Math.Round(Convert.ToDecimal(temp.Substring(0, temp.IndexOf("/"))), 0));
                    //for (int i = 1; i <= temp.Length; i++)
                    //{
                    //    if (temp.Substring(0, i).EndsWith("/"))
                    //    {
                    //        title.Rating = Convert.ToInt32(temp.Substring(0, temp.IndexOf('/')));
                    //        i = temp.Length + 1;
                    //    }
                    //}
                    //page = page.Substring(index);
                }
            }
            catch (Exception ex) 
            {
            }
        }

        /// <summary>
        /// Gets the director from IMDb
        /// </summary>
        private void parseDirector()
        {
            ind = -1;
            temp = "";
            ind = page.IndexOf("Director:");
            if (ind != -1)
            {
                bool b = true;
                temp = page.Substring(ind + 15, 150);
                index = ind + 15 + temp.Length;
                temp = temp.Substring(temp.IndexOf("\">") + 2);
                for (int i = 1; (i <= temp.Length) || (b); i++)
                {
                    if (temp.Substring(0, i).EndsWith("<"))
                    {
                        try
                        {
                            title.Directors.Add(new Director(temp.Substring(0, i - 1)));
                            break;
                        }
                        catch (Exception)
                        {
                            b = false;
                        }
                    }
                }
                page = page.Substring(index);
            }
            else if ((ind = page.IndexOf("Directors:")) != -1)
            {
                bool b = true;
                temp = page.Substring(ind + 15, 150);
                index = ind + 15 + temp.Length;
                temp = temp.Substring(temp.IndexOf("\">") + 2);
                for (int i = 1; (i <= temp.Length) || (b); i++)
                {
                    if (temp.Substring(0, i).EndsWith("<"))
                    {
                        try
                        {
                            title.Directors.Add(new Director(temp.Substring(0, i - 1)));
                            //title.Director = temp.Substring(0, i - 1);
                            break;
                        }
                        catch (Exception)
                        {
                            b = false;
                        }
                    }
                }
                page = page.Substring(index);
            }
        }

        /// <summary>
        /// Gets the genres from IMDb
        /// </summary>
        private void parseGenres()
        {
            ind = -1;
            temp = "";
            ind = page.IndexOf("Genre:");
            if (ind != -1)
            {
                int j = 0;
                bool b = true;
                temp = page.Substring(ind + 35);
                temp = temp.Substring(temp.IndexOf(">") + 1, temp.IndexOf(">more<"));
                index = ind + 35 + temp.Length;
                for (int i = 1; (i <= temp.Length && j < 3) || (b); i++)
                {
                    try
                    {
                        if (temp.Substring(0, i).EndsWith("<"))
                        {

                            t_genres[j] = temp.Substring(0, i - 1);
                            j++;
                            temp = temp.Substring(temp.IndexOf("<a href") + 10);
                            temp = temp.Substring(temp.IndexOf(">") + 1);
                            i = 0;
                        }
                    }
                    catch (Exception)
                    {
                        b = false;
                    }
                }
                title.Genres = t_genres;
                page = page.Substring(index);
            }
        }

        /// <summary>
        /// Gets the tagline from IMDb
        /// </summary>
        private void parseTag()
        {
            ind = -1;
            temp = "";
            ind = page.IndexOf("Tagline:</h5>");
            if (ind != -1)
            {
                temp = page.Substring(ind + 14);
                temp = temp.Substring(0, temp.IndexOf("<div class=\"info\">"));
                index = ind + 14 + temp.Length;
                for (int i = 1; i <= temp.Length; i++)
                {
                    if (temp.Substring(0, i).EndsWith("<"))
                    {
                        title.TagLine = temp.Substring(0, i - 1);
                        i = temp.Length + 1;
                    }
                }
                page = page.Substring(index);
            }
        }

        /// <summary>
        /// Gets the plot from IMDb
        /// </summary>
        private void parsePlot()
        {
            ind = -1;
            temp = "";
            ind = page.IndexOf("Plot:");
            if (ind != -1)
            {
                temp = page.Substring(ind + 11);
                for (int i = 1; i <= temp.Length; i++)
                {
                    if (temp.Substring(0, i).EndsWith("<"))
                    {
                        title.Plot = temp.Substring(0, i - 1);
                        i = temp.Length + 1;
                    }
                }
                index = ind + 20 + title.Plot.Length;
                page = page.Substring(index);
            }
        }

        /// <summary>
        /// Gets the actors from IMDb
        /// </summary>
        private void parseActors(string id)
        {
            var htmlWeb = new HtmlWeb();
            var htmlDocument = htmlWeb.Load("http://www.imdb.com/title/tt" + id + "/fullcredits#cast");

            var tables = htmlDocument.DocumentNode.SelectNodes("//table[@class]");
            foreach (var table in tables)
            {
                foreach (var tr in table.ChildNodes)
                {
                    var role = new Role();
                    foreach (var td in tr.ChildNodes)
                    {
                        if (td.Attributes["class"] != null)
                        {
                            switch (td.Attributes["class"].Value ?? string.Empty)
                            {
                                case "nm":
                                    role.RealName = td.ChildNodes[0].InnerText;
                                    break;
                                case "char":
                                    foreach (var a in td.ChildNodes)
                                    {
                                        if (a.Name == "a")
                                        {
                                            if (!String.IsNullOrEmpty(role.Character))
                                                role.Character += " / ";
                                            role.Character += a.InnerText;
                                        }
                                    }
                                    break;
                                default:
                                    break;
                            }
                        }
                    }
                    if (role.RealName != null)
                        title.Cast.Add(role);
                }
            }
        }

        /// <summary>
        /// Gets the runtime from IMDb
        /// </summary>
        private void parseRuntime()
        {
            var regex = new Regex(@"<h5>Runtime:</h5>[A-Za-z0-9-\s-]*min").Match(page).ToString();
           
            if (regex.Length > 0)
            {
                regex = regex.Substring(18, regex.IndexOf(" min") - 18);
                title.Runtime.Value = Convert.ToDecimal(regex);
            }
        }

        #endregion

        public Title GetTitle(string id)
        {
            LoadPage("http://www.imdb.com/title/tt" + id + "/");
            title = new Title();

            parseLink();
            parseTitle();
            parseYear();
            parseCoverLink();
            parseRate();
            parseDirector();
            parseGenres();
            parseTag();
            parsePlot();
            parseRuntime();

            parseActors(id);
            return title;
        }

        /// <summary>
        /// Parses the title page to get info from a movie or tv show
        /// </summary>
        /// <param name="fields">Fields to parse</param>
        /// <param name="titl">Title to update</param>
        /// <param name="w">Thread that executes this parse</param>
        /// <param name="tipo">Defines if it's to search a movie or a tv serie</param>
        public void parseTitlePage(bool[] fields, System.ComponentModel.BackgroundWorker w, int tipo)
        {
            worker = w;
            title = new Title();

            int pos = page.IndexOf("<title>") + 7;
            int l = page.IndexOf("</title>", pos + 1) - pos;
            tit = page.Substring(pos, l);
            bool prs = false;

            if (tipo == 0)
            {
                pos = page.IndexOf("<div id=\"tn15title\">") + 20;
                l = page.IndexOf("</div>", pos + 1) - pos;
                string temp = page.Substring(pos, l);
                if (!temp.Contains("TV series"))
                {
                    prs = true;
                }
                else prs = false;
            }
            else
            {
                pos = page.IndexOf("<div id=\"tn15title\">") + 20;
                l = page.IndexOf("</div>", pos + 1) - pos;
                string temp = page.Substring(pos, l);
                if (temp.Contains("TV series"))
                {
                    prs = true;
                }
                else prs = false;
            }

            if (prs)
            {
                parseLink();
                //title
                if (fields == null || fields[0])
                    parseTitle();

                worker.ReportProgress(2);

                //year
                if (fields == null || fields[1])
                    parseYear();

                //image link
                if (fields == null || fields[9])
                    parseCoverLink();

                //user rating
                if (fields == null || fields[7])
                    parseRate();

                worker.ReportProgress(3);

                //directors
                if (fields == null || fields[2])
                    parseDirector();

                //genre
                if (fields == null || fields[3])
                    parseGenres();

                //tagline
                if (fields == null || fields[4])
                    parseTag();

                worker.ReportProgress(2);

                //plot
                if (fields == null || fields[5])
                    parsePlot();

                //actors
                if (fields == null || fields[6])
                    parseActors("");

                //runtime
                if (fields == null || fields[8])
                    parseRuntime();

                worker.ReportProgress(3);
            }
            else
            {
                worker.ReportProgress(10);
                title = null;
            }
        }
    }
}